goog.provide('lfpug.Factory');
goog.provide('lfpug.Factory.CanVisitFn');
goog.provide('lfpug.Factory.FactoryMethod');
goog.provide('lfpug.Factory.Production');



/**
 * @constructor
 */
lfpug.Factory = function() {
  this.productions_ = [];
};


/**
 * @typedef {function(this:lfpug.Factory, Object):boolean}
 */
lfpug.Factory.CanVisitFn;


/**
 * @typedef {function(this:lfpug.Factory, Object):Object}
 */
lfpug.Factory.FactoryMethod;


/**
 * @typedef {{
 *   visit: lfpug.Factory.FactoryMethod,
 *   canVisit: lfpug.Factory.CanVisitFn
 * }}
 */
lfpug.Factory.Production;


/**
 * @private
 * @type {Array.<lfpug.Factory.Production>}
 */
lfpug.Factory.prototype.productions_;


/**
 * @param {lfpug.Factory.CanVisitFn} canVisitFn
 *        The canVisit function to check if the given factory method
 *        can be used.
 * @param {lfpug.Factory.FactoryMethod} visitFn
 *        The factory method.
 */
lfpug.Factory.prototype.register = function(canVisitFn, visitFn) {
  this.productions_.push({
    canVisit: canVisitFn,
    visit: visitFn
  });
};


/**
 * @param {...*} var_args
 *        The arguments Arguments.
 *
 * @return {Object}
 *         Returns the constructed object.
 */
lfpug.Factory.prototype.create = function(var_args) {

  /**
   * @type {lfpug.Factory.Production}
   */
  var production;

  /** @type {number} */
  var a = this.productions_.length;

  // iterate backwards so that productions create last have precedence.
  if (a > 0) {
    while (a--) {
      production = this.productions_[a];
      if (production.canVisit.apply(this, arguments)) {
        return production.visit.apply(this, arguments);
      }
    }
  }

  return null;
};
